//! CMS 管理的接口
use std::default;

use crate::security::LoginUser;
use crate::{
    error::AppResult,
    extract::{Json, ValJson},
    state::AppState,
};
use ane_cms::dao::{ArticleDetailVo, ArticleListVo, CategoryDetailVo, CategoryDo};
use ane_cms::dao::{ArticleDo, Manager as CmsManager};
use ane_cms::entity::{cms_article, cms_article_category, cms_category};
use ane_system::dao::user;
use axum::http::status;
use axum::routing::{get, post, put};
use axum::{
    extract::{Path, Query, State},
    response::IntoResponse,
    Extension,
};
use common::page::{PageInfo, PageParams};
use gen_table::model::Orders;
use gen_table::{JsonMap, JsonValue};
use loginmanager::AuthUser;
use sea_orm::sea_query::Cond;
use sea_orm::{ColumnTrait, ConnectionTrait, PaginatorTrait, QueryOrder, QuerySelect, QueryTrait};
use sea_orm::{EntityTrait, QueryFilter};
use serde::Deserialize;
use tracing::event;
use validator::Validate;

/// ## CMS 管理的接口
///
/// - `{prefix}/article/`                 文章列出 新建
/// - `{prefix}/article/:id`              文章获取 更新 删除
/// - `{prefix}/review`                            文章审核
/// - `{prefix}/category/`                      分类列出 新建
/// - `{prefix}/category/:id`                 分类获取 更新 删除
///
/// ## 请求的资源
/// - `{prefix}/article/\d*(/(recycle|restore|post))?$`
/// - `{prefix}/article/(un)?publish/\d*$`

pub fn get_route(prefix: &str) -> axum::Router<AppState> {
    let f = move |p: &str| -> String { format!("{}{p}", prefix) };
    #[cfg_attr(rustfmt, rustfmt_skip)]
    axum::Router::new()
        .route(&f("/statistic"), get(statistic))
        .route(&f("/article/"), get(article_list).post(create_article))
        .route(&f("/article/:id"),get(fetch_article).put(update_article).delete(delete_article),)
        .route(&f("/review"), get(review_list).put(review_action))
        .route(&f("/category/"), get(list_category).post(create_category))
        .route(&f("/category/:id"),get(fetch_category).put(update_category).delete(delete_category) )
}

/// article和用户及单位关系绑定的sql
fn article_sql() -> sea_orm::Select<ane_cms::entity::cms_article::Entity> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    use ane_system::entity::{sys_dept, sys_user};
    Entity::find()
        .join(
            sea_orm::JoinType::InnerJoin,
            Entity::belongs_to(sys_user::Entity)
                .from(Column::UserId)
                .to(sys_user::Column::UserId)
                .into(),
        )
        .join(
            sea_orm::JoinType::InnerJoin,
            sys_user::Entity::belongs_to(sys_dept::Entity)
                .from(sys_user::Column::DeptId)
                .to(sys_dept::Column::DeptId)
                .into(),
        )
        .filter(sys_user::Column::DelFlag.eq(0))
        .filter(sys_dept::Column::DelFlag.eq(0))
}

/// 检查用户是否能访问当前的文章
async fn can_access_article(
    id: i32,
    user: &LoginUser,
    db: &impl ConnectionTrait,
) -> Result<(), crate::AppError> {
    let count = article_sql()
        .filter(cms_article::Column::Id.eq(id))
        .filter(user.dept_cond())
        .count(db)
        .await?;
    Ok(if count < 1 {
        return Err(crate::AppError::Forbidden);
    })
}

pub async fn statistic(
    AuthUser(user): AuthUser<LoginUser>,
    State(state): State<AppState>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    article_sql()
        .filter(user.dept_cond())
        .select_only()
        .column(Column::Status)
        .expr(Column::Status.count())
        .group_by(Column::Status)
        .into_tuple::<(u8, u32)>()
        .all(state.db())
        .await
        .map(|d| {
            let mut s = serde_json::Map::new();
            for (ty, count) in d {
                s.insert(format!("{ty}"), serde_json::to_value(count).unwrap());
            }
            s
        })
        .map(Json)
        .map_err(Into::into)
}

#[derive(Deserialize)]
pub struct ListFilter {
    category: Option<i32>,
    status: Option<i8>,
    #[serde(default)]
    deleted: bool,
    #[serde(default)]
    order: String,
}

pub async fn article_list(
    State(state): State<AppState>,
    Query(page_params): Query<PageParams>,
    Query(filter): Query<ListFilter>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    let mut sql = article_sql()
        .apply_if(filter.category, |sql, category| {
            sql.inner_join(cms_article_category::Entity)
                .filter(cms_article_category::Column::CategoryId.eq(category))
        })
        .apply_if(filter.status, |sql, v| sql.filter(Column::Status.eq(v)))
        .filter(user.dept_cond());
    sql = if filter.deleted {
        sql.filter(Column::Status.gte(64))
    } else {
        sql.filter(Column::Status.lt(64))
    };
    for (key, asc) in Orders::from(filter.order.as_str()).iter() {
        if key == "publish_at" {
            sql = if *asc {
                sql.order_by_asc(Column::PublishAt)
            } else {
                sql.order_by_desc(Column::PublishAt)
            }
        }
    }
    sql = sql.order_by_desc(Column::PublishAt);
    let sql = sql
        .into_partial_model::<ArticleListVo>()
        .paginate(state.db(), page_params.page_size());
    let total = sql.num_items().await?;
    let datas = sql.fetch_page(page_params.zero_page()).await?;
    Ok(Json(PageInfo::new(datas, &page_params, total)).into_response())
}

fn mmap<T, T2>(d: T, key: String, v: T2) -> Result<serde_json::Value, serde_json::Error>
where
    T: serde::Serialize,
    T2: serde::Serialize,
{
    let mut s = serde_json::to_value(d)?;
    if let Some(sd) = s.as_object_mut() {
        let v = serde_json::to_value(v)?;
        sd.insert(key, v);
    }
    Ok(s)
}

pub async fn create_article(
    State(state): State<AppState>,
    AuthUser(user): AuthUser<LoginUser>,
    ValJson(mut data): ValJson<ArticleDo>,
) -> AppResult<impl IntoResponse> {
    // 创建和修改文章不能改变发布状态，方便以后做权限控制
    data.status = None;
    if data.author.is_none() {
        data.author = Some(user.nickname);
    }
    CmsManager::create_article(state.db(), user.id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn fetch_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    let article = article_sql()
        .filter(Column::Id.eq(id))
        .filter(user.dept_cond())
        .into_partial_model::<ArticleDetailVo>()
        .one(state.db())
        .await?;
    let categories = if let Some(m) = &article {
        CmsManager::fetch_article_category_id(state.db(), m.id, None).await?
    } else {
        vec![]
    };
    Ok(Json(mmap(article, "categories".to_string(), categories)?))
}

#[derive(Debug, Deserialize)]
pub enum Action {
    Recycle,
    Restore,
    Post,
    Cancel,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum UpdateArticleDo {
    Action { action: Action },
    Update(ArticleDo),
}

pub async fn update_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
    Json(mut data): Json<UpdateArticleDo>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{self, ActiveModel, Column, Entity};
    use sea_orm::prelude::*;

    can_access_article(id, &user, state.db()).await?;
    match data {
        UpdateArticleDo::Update(mut data) => {
            data.validate()?;
            // 创建和修改文章不能改变发布状态，方便以后做权限控制
            data.status = None;
            CmsManager::update_article(state.db(), None, id, data)
                .await
                .map(Json)
                .map(IntoResponse::into_response)
                .map_err(Into::into)
        }
        UpdateArticleDo::Action { action } => match action {
            Action::Recycle => Entity::update_many()
                .col_expr(Column::Status, Expr::col(Column::Status).add(64))
                .filter(Column::Id.eq(id))
                .filter(Column::Status.lt(64))
                .exec(state.db())
                .await
                .map(|r| r.rows_affected)
                .map(Json)
                .map(IntoResponse::into_response)
                .map_err(Into::into),
            Action::Restore => Entity::update_many()
                .col_expr(Column::Status, Expr::col(Column::Status).sub(64))
                .filter(Column::Id.eq(id))
                .filter(Column::Status.gte(64))
                .exec(state.db())
                .await
                .map(|r| r.rows_affected)
                .map(Json)
                .map(IntoResponse::into_response)
                .map_err(Into::into),
            Action::Post => Entity::update(ActiveModel {
                id: sea_orm::Set(id),
                status: sea_orm::Set(1),
                ..Default::default()
            })
            .filter(Column::Status.lt(64))
            .exec(state.db())
            .await
            .map(Json)
            .map(IntoResponse::into_response)
            .map_err(Into::into),
            Action::Cancel => Entity::update(ActiveModel {
                id: sea_orm::Set(id),
                status: sea_orm::Set(0),
                ..Default::default()
            })
            .filter(Column::Status.lt(64))
            .exec(state.db())
            .await
            .map(Json)
            .map(IntoResponse::into_response)
            .map_err(Into::into),
        },
    }
}

pub async fn delete_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    can_access_article(id, &user, state.db()).await?;
    ane_cms::dao::Manager::del_article(state.db(), None, id)
        .await
        .map(Json)
        .map_err(Into::into)
}

/// 审核文章的列表
pub async fn review_list(
    State(state): State<AppState>,
    Query(page_params): Query<PageParams>,
    Query(filter): Query<ListFilter>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    let mut sql = article_sql()
        .apply_if(filter.category, |sql, category| {
            sql.inner_join(cms_article_category::Entity)
                .filter(cms_article_category::Column::CategoryId.eq(category))
        })
        .apply_if(filter.status, |sql, v| sql.filter(Column::Status.eq(v)))
        .filter(user.dept_cond())
        .filter(
            Cond::any()
                .add(Column::Status.eq(1))
                .add(Column::Status.eq(3)),
        );
    for (key, asc) in Orders::from(filter.order.as_str()).iter() {
        if key == "publish_at" {
            sql = if *asc {
                sql.order_by_asc(Column::PublishAt)
            } else {
                sql.order_by_desc(Column::PublishAt)
            }
        }
    }
    sql = sql.order_by_desc(Column::PublishAt);
    let sql = sql
        .into_partial_model::<ArticleListVo>()
        .paginate(state.db(), page_params.page_size());
    let total = sql.num_items().await?;
    let datas = sql.fetch_page(page_params.zero_page()).await?;
    Ok(Json(PageInfo::new(datas, &page_params, total)).into_response())
}

#[derive(Debug, Clone, Deserialize)]
pub enum ReviewAction {
    Publish,
    Reject,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ReviewEvent {
    id: i32,
    action: ReviewAction,
}

/// 审核文章的操作
pub async fn review_action(
    State(state): State<AppState>,
    AuthUser(user): AuthUser<LoginUser>,
    Json(event): Json<ReviewEvent>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{ActiveModel, Column, Entity};
    use ane_system::entity::{sys_dept, sys_user};
    can_access_article(event.id, &user, state.db()).await?;
    let status = match event.action {
        ReviewAction::Publish => 2,
        ReviewAction::Reject => 3,
    };
    let model = ActiveModel {
        id: sea_orm::Set(event.id),
        status: sea_orm::Set(status),
        ..Default::default()
    };
    Entity::update(model)
        .filter(Column::Status.lt(64))
        .exec(state.db())
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn unpublish_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    can_access_article(id, &user, state.db()).await?;
    let data = ArticleDo {
        status: Some(0),
        ..Default::default()
    };
    CmsManager::update_article(state.db(), None, id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn list_category(State(state): State<AppState>) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Cms::fetch_category(state.db(), None)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn fetch_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::fetch_category::<CategoryDetailVo>(state.db(), id)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn update_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    ValJson(data): ValJson<CategoryDo>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::update_category(state.db(), id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn delete_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::del_category(state.db(), id)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn create_category(
    State(state): State<AppState>,
    ValJson(data): ValJson<CategoryDo>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::add_category(state.db(), data)
        .await
        .map(Json)
        .map_err(Into::into)
}
